/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.ui.modeler.rest.app;

import java.io.InputStream;
import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.idm.api.User;
import org.flowable.ui.common.security.SecurityUtils;
import org.flowable.ui.common.service.exception.BadRequestException;
//import org.flowable.ui.common.service.exception.ConflictingRequestException;
import org.flowable.ui.common.service.exception.InternalServerErrorException;
import org.flowable.ui.modeler.domain.Model;
import org.flowable.ui.modeler.model.ModelKeyRepresentation;
import org.flowable.ui.modeler.model.ModelRepresentation;
import org.flowable.ui.modeler.repository.ModelRepository;
import org.flowable.ui.modeler.serviceapi.ModelService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
 * 该部分重写为了修改流程模板的保存策略
 * @author yweijian
 * @date 2020年1月10日
 * @version 1.0
 */
@RestController
@RequestMapping("/app")
public class ModelResource {

	private static final Logger LOGGER = LoggerFactory.getLogger(ModelResource.class);

	private static final String RESOLVE_ACTION_OVERWRITE = "overwrite";
	private static final String RESOLVE_ACTION_SAVE_AS = "saveAs";
	private static final String RESOLVE_ACTION_NEW_VERSION = "newVersion";

	@Autowired
	protected ModelService modelService;

	@Autowired
	protected ModelRepository modelRepository;

	@Autowired
	protected ObjectMapper objectMapper;
	
	protected BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();

	protected BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();

	/**
	 * GET /rest/models/{modelId} -> Get process model
	 */
	@RequestMapping(value = "/rest/models/{modelId}", method = RequestMethod.GET, produces = "application/json")
	public ModelRepresentation getModel(@PathVariable String modelId) {
		return modelService.getModelRepresentation(modelId);
	}

	/**
	 * GET /rest/models/{modelId}/thumbnail -> Get process model thumbnail
	 */
	@RequestMapping(value = "/rest/models/{modelId}/thumbnail", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE)
	public byte[] getModelThumbnail(@PathVariable String modelId) {
		Model model = modelService.getModel(modelId);
		return model.getThumbnail();
	}

	/**
	 * PUT /rest/models/{modelId} -> update process model properties
	 */
	@RequestMapping(value = "/rest/models/{modelId}", method = RequestMethod.PUT)
	public ModelRepresentation updateModel(@PathVariable String modelId, @RequestBody ModelRepresentation updatedModel) {
		// Get model, write-permission required if not a favorite-update
		Model model = modelService.getModel(modelId);

		ModelKeyRepresentation modelKeyInfo = modelService.validateModelKey(model, model.getModelType(), updatedModel.getKey());
		if (modelKeyInfo.isKeyAlreadyExists()) {
			throw new BadRequestException("Model with provided key already exists " + updatedModel.getKey());
		}

		try {
			updatedModel.updateModel(model);

			if (model.getModelType() != null) {
				ObjectNode modelNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
				modelNode.put("name", model.getName());
				modelNode.put("key", model.getKey());

				if (Model.MODEL_TYPE_BPMN == model.getModelType()) {
					ObjectNode propertiesNode = (ObjectNode) modelNode.get("properties");
					propertiesNode.put("process_id", model.getKey());
					propertiesNode.put("name", model.getName());
					if (StringUtils.isNotEmpty(model.getDescription())) {
						propertiesNode.put("documentation", model.getDescription());
					}
					modelNode.set("properties", propertiesNode);
				}
				model.setModelEditorJson(modelNode.toString());
			}

			modelRepository.save(model);
			
			ModelRepresentation result = new ModelRepresentation(model);
			return result;

		} catch (Exception e) {
			throw new BadRequestException("Model cannot be updated: " + modelId);
		}
	}

	/**
	 * DELETE /rest/models/{modelId} -> delete process model or, as a non-owner, remove the share info link for that user specifically
	 */
	@ResponseStatus(value = HttpStatus.OK)
	@RequestMapping(value = "/rest/models/{modelId}", method = RequestMethod.DELETE)
	public void deleteModel(@PathVariable String modelId) {

		// Get model to check if it exists, read-permission required for delete
		Model model = modelService.getModel(modelId);

		try {
			
			modelService.deleteModel(model.getId());

		} catch (Exception e) {
			LOGGER.error("Error while deleting: ", e);
			throw new BadRequestException("Model cannot be deleted: " + modelId);
		}
	}

	/**
	 * GET /rest/models/{modelId}/editor/json -> get the JSON model
	 */
	@RequestMapping(value = "/rest/models/{modelId}/editor/json", method = RequestMethod.GET, produces = "application/json")
	public ObjectNode getModelJSON(@PathVariable String modelId) {
		Model model = modelService.getModel(modelId);
		ObjectNode modelNode = objectMapper.createObjectNode();
		modelNode.put("modelId", model.getId());
		modelNode.put("name", model.getName());
		modelNode.put("key", model.getKey());
		modelNode.put("description", model.getDescription());
		modelNode.putPOJO("lastUpdated", model.getLastUpdated());
		modelNode.put("lastUpdatedBy", model.getLastUpdatedBy());
		if (StringUtils.isNotEmpty(model.getModelEditorJson())) {
			try {
				ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(model.getModelEditorJson());
				editorJsonNode.put("modelType", "model");
				modelNode.set("model", editorJsonNode);
			} catch (Exception e) {
				LOGGER.error("Error reading editor json {}", modelId, e);
				throw new InternalServerErrorException("Error reading editor json " + modelId);
			}

		} else {
			ObjectNode editorJsonNode = objectMapper.createObjectNode();
			editorJsonNode.put("id", "canvas");
			editorJsonNode.put("resourceId", "canvas");
			ObjectNode stencilSetNode = objectMapper.createObjectNode();
			stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
			editorJsonNode.put("modelType", "model");
			modelNode.set("model", editorJsonNode);
		}
		return modelNode;
	}

	/**
	 * POST /rest/models/{modelId}/editor/json -> save the JSON model
	 */
	@RequestMapping(value = "/rest/models/{modelId}/editor/json", method = RequestMethod.POST)
	public ModelRepresentation saveModel(@PathVariable String modelId, @RequestBody MultiValueMap<String, String> values) {

		// Validation: see if there was another update in the meantime
		long lastUpdated = -1L;
		String lastUpdatedString = values.getFirst("lastUpdated");
		if (lastUpdatedString == null) {
			throw new BadRequestException("Missing lastUpdated date");
		}
		try {
			Date readValue = objectMapper.getDeserializationConfig().getDateFormat().parse(lastUpdatedString);
			lastUpdated = readValue.getTime();
		} catch (ParseException e) {
			throw new BadRequestException("Invalid lastUpdated date: '" + lastUpdatedString + "'");
		}

		Model model = modelService.getModel(modelId);
		User currentUser = SecurityUtils.getCurrentUserObject();
		boolean currentUserIsOwner = model.getLastUpdatedBy().equals(currentUser.getId());
		String resolveAction = values.getFirst("conflictResolveAction");

		// If timestamps differ, there is a conflict or a conflict has been resolved by the user
		if (model.getLastUpdated().getTime() != lastUpdated) {

			if (RESOLVE_ACTION_SAVE_AS.equals(resolveAction)) {

				String saveAs = values.getFirst("saveAs");
				String json = values.getFirst("json_xml");
				return createNewModel(saveAs, model.getDescription(), model.getModelType(), json);

			} else if (RESOLVE_ACTION_OVERWRITE.equals(resolveAction)) {
				return updateModel(model, values, false);
			} else if (RESOLVE_ACTION_NEW_VERSION.equals(resolveAction)) {
				return updateModel(model, values, true);
			} else {

				// Exception case: the user is the owner and selected to create a new version
				String isNewVersionString = values.getFirst("newversion");
				if (currentUserIsOwner && "true".equals(isNewVersionString)) {
					return updateModel(model, values, true);
				} else {
					// Tried everything, this is really a conflict, return 409
					/*ConflictingRequestException exception = new ConflictingRequestException("Process model was updated in the meantime");
                    exception.addCustomData("userFullName", model.getLastUpdatedBy());
                    exception.addCustomData("newVersionAllowed", currentUserIsOwner);
                    throw exception;*/
					
					//自定义修改保存策略
					return updateModel(model, values, false);
				}
			}

		} else {

			// Actual, regular, update
			return updateModel(model, values, false);

		}
	}

	/**
	 * POST /rest/models/{modelId}/editor/newversion -> create a new model version
	 */
	@RequestMapping(value = "/rest/models/{modelId}/newversion", method = RequestMethod.POST)
	public ModelRepresentation importNewVersion(@PathVariable String modelId, @RequestParam("file") MultipartFile file) {
		InputStream modelStream = null;
		try {
			modelStream = file.getInputStream();
		} catch (Exception e) {
			throw new BadRequestException("Error reading file inputstream", e);
		}

		return modelService.importNewVersion(modelId, file.getOriginalFilename(), modelStream);
	}

	protected ModelRepresentation updateModel(Model model, MultiValueMap<String, String> values, boolean forceNewVersion) {

		String name = values.getFirst("name");
		String key = values.getFirst("key").replaceAll(" ", "");
		String description = values.getFirst("description");
		String isNewVersionString = values.getFirst("newversion");
		String newVersionComment = null;

		ModelKeyRepresentation modelKeyInfo = modelService.validateModelKey(model, model.getModelType(), key);
		if (modelKeyInfo.isKeyAlreadyExists()) {
			throw new BadRequestException("Model with provided key already exists " + key);
		}

		boolean newVersion = false;
		if (forceNewVersion) {
			newVersion = true;
			newVersionComment = values.getFirst("comment");
		} else {
			if (isNewVersionString != null) {
				newVersion = "true".equals(isNewVersionString);
				newVersionComment = values.getFirst("comment");
			}
		}

		String json = values.getFirst("json_xml");

		try {
			ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(json);

			ObjectNode propertiesNode = (ObjectNode) editorJsonNode.get("properties");
			String processId = key;
			propertiesNode.put("process_id", processId);
			propertiesNode.put("name", name);
			if (StringUtils.isNotEmpty(description)) {
				propertiesNode.put("documentation", description);
			}
			editorJsonNode.set("properties", propertiesNode);
			model = modelService.saveModel(model.getId(), name, key, description, editorJsonNode.toString(), newVersion,
					newVersionComment, SecurityUtils.getCurrentUserObject());
			
			return new ModelRepresentation(model);

		} catch (Exception e) {
			LOGGER.error("Error saving model {}", model.getId(), e);
			throw new BadRequestException("Process model could not be saved " + model.getId());
		}
	}

	protected ModelRepresentation createNewModel(String name, String description, Integer modelType, String editorJson) {
		ModelRepresentation model = new ModelRepresentation();
		model.setName(name);
		model.setDescription(description);
		model.setModelType(modelType);
		Model newModel = modelService.createModel(model, editorJson, SecurityUtils.getCurrentUserObject());
		
		return new ModelRepresentation(newModel);
	}
}
